package ecomp.uefs.micc.sharedbox.model;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.swing.JOptionPane;
import org.apache.commons.mail.EmailException;

/**
 *
 * @author emanuellima
 */

/*
 *  Códigos de identificação dos Métodos Client/Server:
 *  12 => Login
 *  16 => Registration
 *  
 *  Códigos de identificação dos Métodos Server/Client:
 *  13 => Login correto
 *  14 => Senha errada
 *  15 => E-mail não cadastrado
 * 
 */
public class Server extends Thread {

    protected static ServerSocket server;
    private static int port = 12345;    
    private Socket client;
    private static List<User> USERS_LIST;
    private static List<String> LOGGED_LIST;
    private static List<SharedFile> SHAREDSFILES_LIS;
    private static String directoryFiles;
    public int userindex;
    private User userLog;

    public Server(Socket socket) {        
        this.client = socket;
    }

    public Server() {
    }

    /**
     *
     */
    public static void main(String args[]) {
        log("Inicializando Configurações do Servidor...");
        try {
            // instancia o vetor de CLIENTES conectados
            LOGGED_LIST = new ArrayList<>();
            loadUsersList();
            read_SHAREDSFILES_LIST();
        } catch (IOException | ClassNotFoundException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
        loadDir();
        
        try {
            // cria um socket que fica escutando a porta 5555.            
            server = new ServerSocket(port);
             log("Servidor Iniciado na Porta 12345. Aguardando Conexões...");
            // JOptionPane.showMessageDialog(null, "FTP Server Started on Port Number "+ port, "Server Runing", 1);

            // Loop principal.
            while (true) {
                // aguarda algum cliente se conectar.
                // A execução do servidor fica bloqueada     na chamada do método accept da
                // classe ServerSocket até que algum cliente se conecte ao servidor.
                // O próprio método desbloqueia e retorna com um objeto da classe Socket
                Socket client = server.accept();
                log("Nova Conexão com o Cliente FTP " + client.getInetAddress());
                //          JOptionPane.showMessageDialog(null,"New Conection With Client" 
                //                                  + client.getInetAddress(),"Server Runing", 1);
                // cria uma nova thread para tratar essa conexão
                Thread threadClient = new Server(client);
                threadClient.start();
                // voltando ao loop, esperando mais alguém se conectar.
            }
        } catch (IOException e) {
            // caso ocorra alguma excessão de E/S, mostre qual foi.
            System.out.println("IOException: " + e);
        }
    }
    
    /**
     * Executa as requisições dos clientes: recebe o codigo correspondente
     * de cada método do cliente e verifica qual método deve ser chamado...
     * 
     */

    @Override
    public void run() {
        
        while (!client.isClosed()) {
            log("Aguardando Requisições...");
            DataInputStream input = null;
            int code = 0;
            try {
                input = new DataInputStream(client.getInputStream());
                code = input.readInt();
            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            }            

            System.out.println(code);
            
            switch(code){
                
                case 10:
                    log("Nova Requisição de DISCONNECT");
                    try {
                        client.close();
                        System.out.println("FTP Client " + client.getInetAddress() + " Disconected");
                    } catch (IOException ex) {
                        Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    break;
                
                case 12:
                    log("Nova Requisição de LOGIN ");                    
                    login();
                    break;
                
                case 16:
                    log("Nova Requisição de REGISTRATION ");                    
                    register();
                    break;
                    
                case 22:
                    log("Nova Requisição de UPLOAD ");                   
                    try {
                        receiveFile();
                        //sender(30);
                        //sendFileList(userLog.getServerpath());
                        log("Enviou lista de arquivos");
                    } catch (IOException ex) {
                        Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    break;
                case 25:
                    log("Nova Requisição de DOWNLOAD ");                    
                    sendFile();
                    break;
                case 40:
                    log("Nova Requisição de SHARING ");
                    receiveSharedFile();
                    break;
                default:
                    System.out.println("Comando não Identificado..");
                    
            }
        }

    }
    
        /**
     * Exibe informações no console
     * @param msgm 
     */
     private static void log(String msgm) {
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");
        System.out.println(sdf.format(new Date()).toUpperCase() + " => " + msgm);
    }

    public void close() {
        try {
            server.close();
            System.out.println();
        } catch (IOException e) {
        }
    }

    public int getPort() {
        return port;
    }
    
   
    
    /**
     * 
     * @throws IOException 
     */

    public void receiveFile() throws IOException{                   

        DataInputStream input = null;
        DataOutputStream output = null;
        try {
            input = new DataInputStream(client.getInputStream());
            output = new DataOutputStream(client.getOutputStream());
            String ownerMail = input.readUTF();
            long size = input.readLong();
            String filename = input.readUTF();

            File path = new File(directoryFiles  + System.getProperties().get("file.separator") + ownerMail
                    + System.getProperties().get("file.separator"));
            try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(path, filename)))) {
                byte[] buffer = new byte[4096];

                //Envia os bytes  
                Integer leitura = null;
                Integer count = 0;
                do {
                    leitura = input.read(buffer, 0, buffer.length);
                    count += leitura;
                    bos.write(buffer, 0, leitura);
                    bos.flush();

                } while (leitura != -1 && count < size);
                bos.close();
                System.out.println("Arquivo Recebido");
                sender(30);
                sendFileList(directoryFiles + System.getProperties().get("file.separator") + ownerMail);
            }
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
        

    }
    
    /**
     * 
     */
    
    public void sendFile(){
        
        DataInputStream input;
        try {
            input = new DataInputStream(client.getInputStream());        
            String name = input.readUTF();
            String owner = input.readUTF();
            File file = new File(directoryFiles + System.getProperties().get("file.separator") 
                    + owner + System.getProperties().get("file.separator") + name);        
        try {
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            
            try {
                DataOutputStream output = new DataOutputStream(client.getOutputStream());
             //   output.writeInt(22);              
                output.writeLong(file.length());
                output.writeUTF(file.getName());
                System.out.println(file.getName());
                output.flush();
              
                //Cria um buffer  
                byte[] buffer = new byte[4046]; 
                //Envia dados  
                    Integer leitura = null;  
                    do {  
                        leitura = bis.read(buffer, 0, buffer.length);  
                        System.out.flush();  
                        if (leitura != -1) {  
                            output.write(buffer, 0, leitura);  
                        } else {  
                            output.write(leitura);  
                        }  
                        output.flush();  
  
                    } while (leitura != -1);
                    
            } catch (IOException ex) {
                Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
            }
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    /**
     * Exibe Usuarios no console
     */

    public void showUsers() {
        System.out.println("Lista de Usuarios:");
        for (int s = 0; s < USERS_LIST.size(); s++) {
            System.out.println(USERS_LIST.get(s).getEmail());
        }
    }
    
    /**
     * Método Utilizado para enviar e-mails
     * @throws EmailException 
     */

    public void sendEmail() throws EmailException {

        Properties props = new Properties();
        props.put("mail.smtp.host", "smtp.gmail.com");
        props.put("mail.smtp.socketFactory.port", "465");
        props.put("mail.smtp.socketFactory.class",
                "javax.net.ssl.SSLSocketFactory");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.port", "465");

        Session session = Session.getDefaultInstance(props,
                new javax.mail.Authenticator() {
            @SuppressWarnings("deprecation")
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(
                        "emanuel.uefs@gmail.com", "password");
            }
        });

        /**
         * Ativa Debug para sessão
         */
        session.setDebug(true);
        try {

            Message message = new MimeMessage(session);
            message.setFrom(new InternetAddress("emanuel.uefs@gmail.com")); // Remetente

            Address[] toUser = InternetAddress // Destinatário(s)
                    .parse("ejl.ecomp@gmail.com, ejl.ecomp@hotmail.com");

            message.setRecipients(Message.RecipientType.TO, toUser);
            message.setSubject("Enviando email com JavaMail");// Assunto
            message.setText("Enviei este email utilizando JavaMail com minha conta GMail!");
            /**
             * Método para enviar a mensagem criada
             */
            Transport.send(message);

            System.out.println("Feito!!!");

        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }

    }
    
    /**
     * Método para Adicionar usuario
     * @param user 
     */

    public void addUser(User user) {
        USERS_LIST.add(user);
    }

    public void starter() {
        // TODO Auto-generated method stub
        try {

            server = new ServerSocket(port);
            JOptionPane.showMessageDialog(null, "Server listening on port "
                    + port, "Server Runing", 1);
            System.out.println();
            while (true) {

                Socket client = server.accept();
                JOptionPane.showMessageDialog(null, "New Conection With Client"
                        + client.getInetAddress(), "Server Runing", 1);
                Thread t = new Server(client);
                t.start();
            }
        } catch (IOException e) {
        }

    }
    
    /**
     * Método que realiza o login no servidor
     */
    
     /**
     * 
     * @param user
     * @return ret
     */

    private int authentication(User user) {
        int ret = 0;
        for (int s = 0; s < USERS_LIST.size(); s++) {
            if (user.getEmail().equals(USERS_LIST.get(s).getEmail())) {
                if (user.getPassword().equals(USERS_LIST.get(s).getPassword())) {
                    userLog = USERS_LIST.get(s);
                   // System.out.println(userLog.getServerpath());
                    ret = 1;
                    
                    break;
                } else {
                    ret = 2;
                    break;
                }

            } else {
            }
        }

        return ret;

    }

    private void login() {
        try {
            ObjectInputStream ois = new ObjectInputStream(client.getInputStream());
            User user = null;
            try {
                user = (User) ois.readObject();
                System.out.println(user.getEmail());
              //  System.out.println(user.getPassword());
              //  System.out.println(authentication(user));

                if (authentication(user) == 1) {
                    System.out.println("Usuario Logado");
                    loadUserDir(userLog);
                    sender(13);                    
                    sendFileList(userLog.getServerpath());
                    sendListEmails();
                    sendSharedFileList(userLog);
                } else if (authentication(user) == 2) {
                    System.out.println("Senha Errada");
                    sender(14);
                } else {
                    System.out.println("E-mail não cadastrado");
                    sender(15);
                }
                
             //   showUsers();
                
            } catch (ClassNotFoundException e) {
            }
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    /**
     * Método para registrar um usuário no Servidor
     */

    private void register() {
        try {
            ObjectInputStream ois = new ObjectInputStream(client.getInputStream());
            User user = (User) ois.readObject();
            System.out.println(authentication(user));
            System.out.println(user.getEmail());
            System.out.println(user.getPassword());
            if (authentication(user) == 0) {
                addUser(user);
                saveUSERS_LIST();
                loadUserDir(user);
                sender(17);
            } else {
                System.out.println("email ja existe");
                sender(18);
            }

//            showUsers();
        } catch (IOException | ClassNotFoundException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }


    }
    
    /**
     * Método Utilizado para enviar códigos de Resposta ao Cliente
     * @param code 
     */

    public void sender(int code) {
        DataOutputStream output;
        try {
            output = new DataOutputStream(client.getOutputStream());
            output.writeInt(code);
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
    
    /**
     * Método utilizado para exibir as requisições no prompt
     * @param str 
     */
    
    private void showLog(String str){
        System.out.println("Received Request " + str);
    }
    
    /**
     * 
     * Envia a lista de Arquivos do servidor
     */
    
    private void sendFileList(String userpath){       
       try {
            DataOutputStream output = new DataOutputStream(client.getOutputStream());
            ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream());
           // output.writeInt(25);           
            File file = new File(userpath); 
            File afile[] = file.listFiles();
            oos.writeObject(afile);
            //output.writeLong(afile.length);
            /*  int i = 0; 
                for (int j = afile.length; i < j; i++) { 
                    File arquivos = afile[i];      
                    output.writeUTF(arquivos.getName());
                    System.out.println(arquivos.getName()); 
                }*/
            output.flush();
        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    /**
     * Envia a lista de emails cadastrados
     */
    
    private void sendListEmails(){
        ArrayList emailList = new ArrayList();
        int i = 0;
        String email;
            for(i =0; i < USERS_LIST.size(); i++){
                email = USERS_LIST.get(i).getEmail();
                emailList.add(email);
            }
        try {
            ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream());
            oos.writeObject(emailList);
            oos.flush();
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    /**
     * Cria o Diretorio padrão para salvar os arquivos
     */
    
    private static void loadDir(){
        String local = System.getProperty("user.home");       
        File file = new File(local +  System.getProperties().get("file.separator") +"SharedboxServer");
        
        if(!file.exists()){
            System.out.println("Criando Diretorio de Arquivos em :" + file.getPath());
            file.mkdirs();
            directoryFiles = file.getPath();
        }
        else{
            System.out.println("Carregando o Diretorio " + file.getPath());
            directoryFiles = file.getPath();
        }
    }
    
    private void loadUserDir(User user){
        String userpath = user.getEmail();               
        File file = new File(directoryFiles +  System.getProperties().get("file.separator") + userpath);
      //  System.out.println(file.getPath());
        if(!file.exists()){            
            file.mkdirs();
            log("Criou Diretorio Do Usuario " + file.getPath());
            user.setServerpath(file.getPath());
        }
        else{
            log("Carregou Diretorio do Usuario " + file.getPath());
            user.setServerpath(file.getPath());
        }
     //   System.out.println(file.getPath() + "/" + getUser().getEmail());
    }
    
    /**
     * Salva a lista de usuarios em um arquivo
     * @throws IOException 
     */
    
    private static void saveUSERS_LIST() throws IOException{
        
        File file = new File("USERS_LIST.obj");        
        ObjectOutputStream objectOut;              
        try {
            objectOut = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
            objectOut.writeObject(USERS_LIST);  
            objectOut.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
               
    }
    
    /**
     * Ler a lista de usuários registrados a partir de um arquivo, caso esse arquivo não exista ele cria uma Lista
     * @throws IOException
     * @throws ClassNotFoundException 
     */
    
    private static void loadUsersList() throws IOException, ClassNotFoundException{
        
        File file = new File("USERS_LIST.obj");
            if(!file.exists()){
                USERS_LIST = new ArrayList<>();
                System.out.println("Lista de Usuários Criada...");
            }
                else{
                    try (ObjectInputStream objectIn = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
                        USERS_LIST = (ArrayList<User>)objectIn.readObject();
                        System.out.println("Carregando Lista de Usuários...");
                    }
                }
        
    }

    public boolean getStates() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    public void setState(boolean b) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
    
    private void receiveSharedFile(){
        
        ObjectInputStream ois;
        try {
            ois = new ObjectInputStream(client.getInputStream());
            try {
                SharedFile sharedFile = (SharedFile)ois.readObject();
                SHAREDSFILES_LIS.add(sharedFile);
                distributeFiles(sharedFile);                
                saveUSERS_LIST();
                sender(30);
                sendSharedFileList(userLog);
            } catch (ClassNotFoundException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            }
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        
    }
    
    private static void read_SHAREDSFILES_LIST() throws IOException, ClassNotFoundException{
        
        File file = new File("SHAREDSFILES_LIST.obj");
            if(!file.exists()){
                SHAREDSFILES_LIS = new ArrayList<>();
                System.out.println("Não Existe");
            }
                else{
                    try (ObjectInputStream objectIn = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
                        SHAREDSFILES_LIS = (ArrayList<SharedFile>)objectIn.readObject();
                        System.out.println("Pegou");
                    }
                }
        
    }
    
    /**
     * 
     * @param file 
     */
    
    private void distributeFiles(SharedFile file){    
        
        for(int i = 0; i < file.getSHARING_LIST().size(); i++){
            for(int j = 0; j < USERS_LIST.size(); j++){
               
                if(file.getSHARING_LIST().get(i).equals(USERS_LIST.get(j).getEmail())){
                    USERS_LIST.get(j).getArquivosCompartilhados().add(file);
                }
            }
        }
    }
    
    /**
     * 
     * @param user 
     */
    
    private void sendSharedFileList(User user){
        DataOutputStream output = null;
        List shlist = user.getArquivosCompartilhados();
        try {
            output = new DataOutputStream(client.getOutputStream());
            ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream());
            oos.writeObject(shlist);
        } catch (IOException ex) {
            Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
        } 
        
    }
   
    
}
